//author Gideon Beyrle
#include "graphics.h"
#include <iostream>

using namespace std;

graphics::graphics()
{
    this->init();
    this->open();
    this->loadShaders();
}

graphics::~graphics(){}

void graphics::loadShaders(){

    programID = LoadShaders("D:/5_Semester/Projektarbeit/openGL_billiard/online_billiard/TransformVertexShader.Vertexshader",
                            "D:/5_Semester/Projektarbeit/openGL_billiard/online_billiard/ColorFragmentShader.fragmentshader");

    programID2 = LoadShaders("D:/5_Semester/Projektarbeit/openGL_billiard/online_billiard/VertexShader3D.Vertexshader",
                             "D:/5_Semester/Projektarbeit/openGL_billiard/online_billiard/FragmentShader3D.fragmentshader");
}

void graphics::init(){
    //VertexDatadata for brown rectangle
    vertexbufferData_BrownRectangle.push_back(VertexData(0.0f,    0.0f,   0.0f));
    vertexbufferData_BrownRectangle.push_back(VertexData(0.0f,    768.0f, 0.0f));
    vertexbufferData_BrownRectangle.push_back(VertexData(1024.0f, 0.0f,   0.0f));
    vertexbufferData_BrownRectangle.push_back(VertexData(1024.0f, 768.0f, 0.0f));
    vertexbufferData_BrownRectangle.push_back(VertexData(1024.0f, 0.0f,   0.0f));
    vertexbufferData_BrownRectangle.push_back(VertexData(0.0f,    768.0f, 0.0f));
    colorbufferData_BrownRectangle.push_back(VertexData(0.450f,   0.290f, 0.070f));
    colorbufferData_BrownRectangle.push_back(VertexData(0.450f,   0.290f, 0.070f));
    colorbufferData_BrownRectangle.push_back(VertexData(0.450f,   0.290f, 0.070f));
    colorbufferData_BrownRectangle.push_back(VertexData(0.450f,   0.290f, 0.070f));
    colorbufferData_BrownRectangle.push_back(VertexData(0.450f,   0.290f, 0.070f));
    colorbufferData_BrownRectangle.push_back(VertexData(0.450f,   0.290f, 0.070f));

    //VertexDatadata for darkgreen ectangle
    vertexbufferData_DarkgreenRectangle.push_back(VertexData(50.0f,  50.0f,  0.0f));
    vertexbufferData_DarkgreenRectangle.push_back(VertexData(50.0f,  718.0f, 0.0f));
    vertexbufferData_DarkgreenRectangle.push_back(VertexData(974.0f, 50.0f,  0.0f));
    vertexbufferData_DarkgreenRectangle.push_back(VertexData(974.0f, 718.0f, 0.0f));
    vertexbufferData_DarkgreenRectangle.push_back(VertexData(50.0f,  718.0f, 0.0f));
    vertexbufferData_DarkgreenRectangle.push_back(VertexData(974.0f, 50.0f,  0.0f));
    colorbufferData_DarkgreenRectangle.push_back(VertexData(0.000f,  0.380f,  0.110f));
    colorbufferData_DarkgreenRectangle.push_back(VertexData(0.000f,  0.380f,  0.110f));
    colorbufferData_DarkgreenRectangle.push_back(VertexData(0.000f,  0.380f,  0.110f));
    colorbufferData_DarkgreenRectangle.push_back(VertexData(0.000f,  0.380f,  0.110f));
    colorbufferData_DarkgreenRectangle.push_back(VertexData(0.000f,  0.380f,  0.110f));
    colorbufferData_DarkgreenRectangle.push_back(VertexData(0.000f,  0.380f,  0.110f));

    //VertexDatadata for green rectangle
    vertexbufferData_GreenRectangle.push_back(VertexData(60.0f, 60.0f,  0.0f));
    vertexbufferData_GreenRectangle.push_back(VertexData(60.0f, 708.0f, 0.0f));
    vertexbufferData_GreenRectangle.push_back(VertexData(964.0f,60.0f,  0.0f));
    vertexbufferData_GreenRectangle.push_back(VertexData(964.0f,708.0f, 0.0f));
    vertexbufferData_GreenRectangle.push_back(VertexData(60.0f, 708.0f, 0.0f));
    vertexbufferData_GreenRectangle.push_back(VertexData(964.0f,60.0f,  0.0f));
    colorbufferData_GreenRectangle.push_back(VertexData(0.000f,  0.550f,  0.000f));
    colorbufferData_GreenRectangle.push_back(VertexData(0.000f,  0.550f,  0.000f));
    colorbufferData_GreenRectangle.push_back(VertexData(0.000f,  0.550f,  0.000f));
    colorbufferData_GreenRectangle.push_back(VertexData(0.000f,  0.550f,  0.000f));
    colorbufferData_GreenRectangle.push_back(VertexData(0.000f,  0.550f,  0.000f));
    colorbufferData_GreenRectangle.push_back(VertexData(0.000f,  0.550f,  0.000f));


}

void graphics::draw(vector<VertexData> vertexData,vector<VertexData> colorData, vector<VertexData> normalData, bool triangle){

    glGenVertexArrays(1, &vertexArrayID);
	glBindVertexArray(vertexArrayID);

    GLuint vertexbuffer;
	glGenBuffers(1, &vertexbuffer);
	glBindBuffer(GL_ARRAY_BUFFER, vertexbuffer);
	glBufferData(GL_ARRAY_BUFFER, sphereVertices.size() * sizeof(VertexData), &sphereVertices[0], GL_STATIC_DRAW);

    GLuint colorbuffer;
	glGenBuffers(1, &colorbuffer);
	glBindBuffer(GL_ARRAY_BUFFER, colorbuffer);
	glBufferData(GL_ARRAY_BUFFER, colorData.size() * sizeof(VertexData), &colorData[0], GL_STATIC_DRAW);

	GLuint normalbuffer;
	glGenBuffers(1, &normalbuffer);
	glBindBuffer(GL_ARRAY_BUFFER, normalbuffer);
	glBufferData(GL_ARRAY_BUFFER,  normals.size() * sizeof(VertexData), &normals[0], GL_STATIC_DRAW);

    glEnableVertexAttribArray(0);
    glBindBuffer(GL_ARRAY_BUFFER, vertexbuffer);
    glVertexAttribPointer(0,3,GL_FLOAT,GL_FALSE,0,(void*)0);

    glEnableVertexAttribArray(1);
    glBindBuffer(GL_ARRAY_BUFFER, colorbuffer);
    glVertexAttribPointer(1,3,GL_FLOAT,GL_FALSE,0,(void*)0);

    glEnableVertexAttribArray(2);
    glBindBuffer(GL_ARRAY_BUFFER, normalbuffer);
    glVertexAttribPointer(2,3,GL_FLOAT,GL_FALSE,0,(void*)0);

    glDrawArrays(GL_TRIANGLES, 0, sphereVertices.size());

    glDisableVertexAttribArray(0);
    glDisableVertexAttribArray(1);
    glDisableVertexAttribArray(2);

    glDeleteBuffers(1, &vertexbuffer);
	glDeleteBuffers(1, &colorbuffer);
	glDeleteBuffers(1, &normalbuffer);
    glDeleteProgram(programID2);
	glDeleteVertexArrays(1, &vertexArrayID);
}


void graphics::draw(vector<VertexData> vertexData,vector<VertexData> colorData, bool triangle){

    glGenVertexArrays(1, &vertexArrayID);
	glBindVertexArray(vertexArrayID);

    GLuint vertexbuffer;
	glGenBuffers(1, &vertexbuffer);
	glBindBuffer(GL_ARRAY_BUFFER, vertexbuffer);
	glBufferData(GL_ARRAY_BUFFER, vertexData.size() * sizeof(VertexData), &vertexData[0], GL_STATIC_DRAW);

    GLuint colorbuffer;
	glGenBuffers(1, &colorbuffer);
	glBindBuffer(GL_ARRAY_BUFFER, colorbuffer);
	glBufferData(GL_ARRAY_BUFFER,  colorData.size() * sizeof(VertexData), &colorData[0], GL_STATIC_DRAW);

    // gives vertexcoordinates to Shader
    glEnableVertexAttribArray(0);
    glBindBuffer(GL_ARRAY_BUFFER, vertexbuffer);
    glVertexAttribPointer(0,3,GL_FLOAT,GL_FALSE,0,(void*)0);

    // gives vertexcolor to Shader
    glEnableVertexAttribArray(1);
    glBindBuffer(GL_ARRAY_BUFFER, colorbuffer);
    glVertexAttribPointer(1,3,GL_FLOAT,GL_FALSE,0,(void*)0);

    // draws the triangles
    if (triangle){glDrawArrays(GL_TRIANGLES, 0, vertexData.size());}
    else {glDrawArrays(GL_TRIANGLE_FAN, 0, vertexData.size());}

    glDisableVertexAttribArray(0);
    glDisableVertexAttribArray(1);

    glDeleteBuffers(1, &vertexbuffer);
	glDeleteBuffers(1, &colorbuffer);
    glDeleteProgram(programID);
	glDeleteVertexArrays(1, &vertexArrayID);
}

int graphics::field(GLfloat holeradius){

    MatrixID = glGetUniformLocation(programID, "MVP");

	glm::mat4 Projection = glm::ortho(0.0f,1024.0f,0.0f,768.0f, 0.1f, 100.0f);
	glm::mat4 View       = glm::lookAt(
								glm::vec3(0,0,1), // Cameralocation
								glm::vec3(0,0,0), // location where the camera looks at
								glm::vec3(0,1,0)  // Head is up (set to 0,-1,0 to look upside-down)
						   );
	glm::mat4 Model      = glm::mat4(1.0f);
                MVP      = Projection * View * Model;

    glUseProgram(programID);
    glUniformMatrix4fv(MatrixID, 1, GL_FALSE, &MVP[0][0]);

    draw(vertexbufferData_BrownRectangle, colorbufferData_BrownRectangle,true);
    draw(vertexbufferData_DarkgreenRectangle, colorbufferData_DarkgreenRectangle,true);
    draw(vertexbufferData_GreenRectangle, colorbufferData_GreenRectangle,true);

    // holes
    bowl(0.0f,0.0f,holeradius,VertexData(0.0f,0.0f,0.0f));
    bowl(0.0f,648.0f,holeradius,VertexData(0.0f,0.0f,0.0f));
    bowl(904.0f,0.0f,holeradius,VertexData(0.0f,0.0f,0.0f));
    bowl(904.0f,648.0f,holeradius,VertexData(0.0f,0.0f,0.0f));
    bowl(452.0f,648.0f,holeradius,VertexData(0.0f,0.0f,0.0f));
    bowl(452.0f,0.0f,holeradius,VertexData(0.0f,0.0f,0.0f));
}

int graphics::close(){
	glfwTerminate();
	return 0;
}

int graphics::open(){

	if( !glfwInit() )
	{
		fprintf( stderr, "Failed to initialize GLFW\n" );
		return -1;
	}

	glfwOpenWindowHint(GLFW_FSAA_SAMPLES, 4);
	glfwOpenWindowHint(GLFW_OPENGL_VERSION_MAJOR, 3);
	glfwOpenWindowHint(GLFW_OPENGL_VERSION_MINOR, 3);
	glfwOpenWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

	if( !glfwOpenWindow( 1280, 800, 0,0,0,0, 32,0, GLFW_WINDOW ) )
	{
		fprintf( stderr, "Failed to open GLFW window.\n" );
		glfwTerminate();
		return -1;
	}

	glewExperimental = true;
	if (glewInit() != GLEW_OK) {
		fprintf(stderr, "Failed to initialize GLEW\n");
		return -1;
	}

	glfwSetWindowTitle( "Online Billard" );

	glfwEnable( GLFW_STICKY_KEYS );
}

int graphics::bowl(GLfloat xCoordinate, GLfloat yCoordinate ,GLfloat radius, VertexData colorData){

	glm::mat4 Projection = glm::ortho(0.0f,1024.0f,0.0f,768.0f, 0.1f, 100.0f);
	glm::mat4 View       = glm::lookAt(
								glm::vec3(0,0,1),
								glm::vec3(0,0,0),
								glm::vec3(0,1,0)
						   );

	glm::mat4 Model      = glm::mat4(1.0f);
	glm::mat4 MVP3        = Projection * View * Model;

	int triangleAmount = 20;

	vector<VertexData> VertexDatabufferData;

	GLfloat twicePi = 2.0f * 3.14159265359f;

    for(int i = 0; i <= triangleAmount;i++) {

        VertexDatabufferData.push_back(VertexData((60 + xCoordinate + (radius * cos(i *  twicePi / triangleAmount))),(60 + yCoordinate + (radius * sin(i * twicePi /  triangleAmount))), 0.0f));

    }

	vector<VertexData> colorbufferData;

	for (int i = 0; i < 20 ; i++){

        colorbufferData.push_back(colorData);

	}

	glUseProgram(programID);

    glUniformMatrix4fv(MatrixID, 1, GL_FALSE, &MVP3[0][0]);

    draw(VertexDatabufferData, colorbufferData, false);
}

void graphics::sphere(GLfloat xCoord, GLfloat yCoord, GLfloat radius, VertexData ColorData){

    glm::mat4 Projection = glm::ortho(0.0f,1024.0f,0.0f,768.0f, 0.1f, 100.0f);
	glm::mat4 View       = glm::lookAt(
								glm::vec3(0,0,1),
								glm::vec3(0,0,0),
								glm::vec3(0,1,0)
						   );

	glm::mat4 Model      = glm::mat4(1.0f);
	glm::mat4 MVP        = Projection * View * Model;

	xCoord += framerange;
	yCoord += framerange;

	vector<VertexData> triangleColor;

    if(!created){

        subdivide(VertexData(xCoord -50.0f , yCoord-25.0f,0.0f), VertexData( xCoord + 50.0f, yCoord -25.0f, 0.0f), VertexData( xCoord, yCoord +25.0f,0.0f), 4);

        for(int i = 0; i < triangleVertices.size(); i++){
            sphereVertices.push_back(normalize(VertexData( xCoord, yCoord,0.0f), triangleVertices[i], radius));

        }
        for(int i = 0; i < sphereVertices.size() / 3; i+=3){
            normals.push_back(normal(sphereVertices[i], sphereVertices[i+1], sphereVertices[i+2]));
        }
        created = true;
    }else{
        for(int i = 0; i < sphereVertices.size(); i++){
            sphereVertices[i].setX(sphereVertices[i].getX() + (xCoord - oldmidpoint.getX()));
            sphereVertices[i].setY(sphereVertices[i].getY() + (yCoord - oldmidpoint.getY()));
            triangleColor.push_back(ColorData);
        }
    }

    glUniformMatrix4fv(MatrixID, 1, GL_FALSE, &MVP[0][0]);

    oldmidpoint = VertexData(xCoord,yCoord,0.0f);

	glUseProgram(programID2);

	draw(sphereVertices, triangleColor, normals, true);

}

VertexData graphics::normal(VertexData a, VertexData b, VertexData c){

    VertexData v1;
    v1.setX(b.getX() - a.getX());
    v1.setY(b.getY() - b.getY());
    v1.setZ(b.getZ() - b.getZ());

    VertexData v2;
    v2.setX(c.getX() - a.getX());
    v2.setY(c.getY() - b.getY());
    v2.setZ(c.getZ() - b.getZ());

    VertexData normalvector;

    normalvector.setX(v1.getY() * v2.getZ() - v1.getZ() * v2.getY());
    normalvector.setY(v1.getZ() * v2.getX() - v1.getX() * v2.getZ());
    normalvector.setZ(v1.getX() * v2.getY() - v1.getY() * v2.getX());

    normalvector = normalize(normalvector, VertexData(0,0,0), 1.0f);

    return normalvector;

}

VertexData graphics::normalize(VertexData a, VertexData b,  GLfloat length){

    GLfloat distance_x = b.getX() - a.getX();
    GLfloat distance_y = b.getY() - a.getY();
    GLfloat distance_z = b.getZ() - a.getZ();

    GLfloat distance_ab = sqrt(pow(distance_x,2) + pow(distance_y,2) + pow(distance_z,2));

    distance_x = distance_x * length / distance_ab;
    distance_y = distance_y * length / distance_ab;
    distance_z = distance_z * length / distance_ab;

    VertexData c = VertexData(a.getX() + distance_x, a.getY() + distance_y, a.getZ() + distance_z);

    return c;
}



void graphics::subdivide(VertexData a, VertexData b, VertexData c, long depth)
{
    VertexData midpoints[3];

    if(depth == 0){

        triangleVertices.push_back(a);
        triangleVertices.push_back(b);
        triangleVertices.push_back(c);

        return;
    }

    midpoints[0].setX((a.getX() + b.getX()) * 0.5f);
    midpoints[1].setX((b.getX() + c.getX()) * 0.5f);
    midpoints[2].setX((c.getX() + a.getX()) * 0.5f);

    midpoints[0].setY((a.getY() + b.getY()) * 0.5f);
    midpoints[1].setY((b.getY() + c.getY()) * 0.5f);
    midpoints[2].setY((c.getY() + a.getY()) * 0.5f);

    midpoints[0].setZ((a.getZ() + b.getZ()) * 0.5f);
    midpoints[1].setZ((b.getZ() + c.getZ()) * 0.5f);
    midpoints[2].setZ((c.getZ() + a.getZ()) * 0.5f);

    depth--;
    subdivide(a,midpoints[0],midpoints[2], depth);
    subdivide(b,midpoints[1],midpoints[0], depth);
    subdivide(c,midpoints[2],midpoints[1], depth);
    subdivide(midpoints[0], midpoints[1], midpoints[2], depth);
}
